#!/usr/bin/env perl

# 
# Copyright (c) 2002-2006 Eric Wallengren
# This file is part of the Continuous Automated Build and Integration 
# Environment (CABIE)
# 
# CABIE is distributed under the terms of the GNU General Public
# License version 2 or any later version.  See the file COPYING for copying 
# permission or http://www.gnu.org. 
#                                                                            
# THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED OR  
# IMPLIED, without even the implied warranty of MERCHANTABILITY or FITNESS 
# FOR A PARTICULAR PURPOSE.  ANY USE IS AT YOUR OWN RISK. 
#                                                                            
# Permission to modify the code and to distribute modified code is granted, 
# provided the above notices are retained, and a notice that the code was 
# modified is included with the above copyright notice. 
# 

use Cwd;
use Sys::Hostname;
use Term::ReadLine;
use Time::Local;
use File::Path;
use DBD::mysql;

my $hostname     = hostname();
$hostname        =~ s/\.[a-zA-Z0-9]+//g;

my $configname   = $hostname;

#
# Get rid of hyphens
#
$configname      =~ s/-//g;

my $basedir = getcwd();

#
# Setup global variables
#
my $POSIX = 1;
my $bNotReady = 0;
my %basepackages;
my $default;
my $type;
my $function;
my $prompt = "Default";
my $term;
my $errstring;
my $installcmd;
my @contents;

my $welcome = "%02d-%02d-%4d: CABIE initially installed and configured ".
              "on $hostname.";

my $now;

$now = time;

my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime($now);
$year += 1900; 
$mon  += 1;

my $systype = $;

if ($systype =~ /MSWin32/) {
    $POSIX   = 0;
    $type    = "win2k";
    $systype = "windows";
} else {
    $type    = "posix";
}

chomp $systype;

my %solutions = (

    1 => "Enter a valid directory, or create the default directory\n\t".
         "then press enter to continue.",
    2 => "Enter a valid URL starting with 'http://' then press enter to\n\t".
         "continue.",
    3 => "Enter a valid group from /etc/group then press enter to continue.",
    4 => "Ensure that the server exists, that MySQL is running on the\n".
         "\tserver, that steps 3-10 from quickstart.txt have been completed\n".
         "\tIf the userid/password are incorrect you will need to re-run \n".
         "\tthis configuration and enter the correct information!",
              

);

if ($POSIX) {
    $installcmd = "perl -MCPAN -e shell install <packagename>";
} else {
    $installcmd = "installpackages.cmd";
}

my %fields = (

    BD            => {
        comment   => "# BuildDrive -",
        editable  => 1,
        help      => "A drive specification or root specification for the ".
                     "device\n\tbuilds will be running on, such as c:/ for ".
                     "Windows or / for\n\tUnix like systems.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkpath",
        },
        default   => {
            posix => "/",
            win2k => "c:/",
        },
    },
    BSR           => {
        comment   => "# BuildServerRoot",
        editable  => 1,
        help      => "The name of the directory containing buildserver.pl.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkpath",
        },
        default   => {
            posix => "$basedir",
            win2k => "$basedir",
        },
    },
    BC            => {
        comment   => "# BuildCommand",
        editable  => 1,
        help      => "The name starting with the complete path, to the ".
                     "build\n\tcommand used by CABIE.  Best to use the ".
                     "default value\n\tfor this parameter.",
        uses      => "BSR:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkfile",
        },
        default   => {
            posix => "bin/builder.pl",
            win2k => "bin/builder.pl",
        },
    },
    BTMP          => { 
        comment   => "# Name of temp directory",
        editable  => 1,
        help      => "The location of tempdir on your system.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkpath",
        },
        default   => {
            posix => "/tmp",
            win2k => "c:/tempdir",
        },
    },
    CVSROOT       => { 
        comment   => "# Place to locate CVS admin files",
        editable  => 1,
        help      => "Name of the directory containing CVS admin files ".
                     "such as\n\tusers, groups etc.",
        uses      => "BSR:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkpath",
        },
        default   => {
            posix => "CVSROOT",
            win2k => "CVSROOT",
        },
    },
    COMPANY       => { 
        comment   => "# Company name (for email)",
        editable  => 1,
        help      => "Name of the company running CABIE.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "CABIE Inc.",
            win2k => "CABIE Inc.",
        },
    },
    DFCMD         => {
        comment   => "# df command, has no affect with win2k",
        editable  => 1,
        help      => "Cmd used to display amount of free space on Unix ".
                     "like systems.\n\tOutput must match that of bin/df ".
                     "-k on Linux:\n\n\t".
                     "/dev/sda6             33855856  25910684   7945172  ".
                     "77% /\n\n\tThis value has no effect on windows.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "/bin/df -k",
            win2k => "NA",
        },
    },
    DEFGROUP      => { 
        comment   => "# Default group for cm systems",
        editable  => 1,
        help      => "Default group ID for user running CABIE.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkgroup",
        },
        default   => {
            posix => "dev",
            win2k => "NA",
        },
    },
    BUGDB         => {
        comment   => "# Bugtracking system web interface specification",
        editable  => 1,
        help      => "URL used for bugtracking including %s used as ".
                     "as a placeholder\n\tfor a bug number used to query ".
                     "a bug tracking system such as\n\tClearquest or ".
                     "Bugzilla.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "http://bugtracker/viewbug?id=%s",
            win2k => "http://bugtracker/viewbug?id=%s",
        },
    },
    WORKDRIVE     => {
        comment   => "# Define workdir and drive",
        editable  => 1,
        help      => "The drive/directory where builds will take place.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkpath",
        },
        default   => {
            posix => "/",
            win2k => "c:/",
        },
    },
    NULL          => {
        comment   => "# Define null device",
        editable  => 1,
        help      => "NULL device used by the OS running CABIE. ".
                     "Unix style OS's\n\tuse /dev/null, Windows typically ".
                     "uses NUL.\n\n\tDon't alter this value unless you're ".
                     "absolutly certain\n\tabout the NULL device on your ".
                     "system.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "/dev/null",
            win2k => "NUL",
        },
    },
    JOBDIR        => {
        comment   => "# Where do the jobs get copied to by the postbuild ".
                     "scripts",
        editable  => 1,
        help      => "Directory where a CABIE generated build is copied ".
                     "upon its\n\tcompletion.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkpath",
        },
        default   => {
            posix => "/usr/builds",
            win2k => "c:/builds",
        },
    },
    W2K           => { 
        comment   => "# Arg to W2k copy command",
        editable  => 1,
        help      => "If running on certain versions of windows you may need ".
                     "to set\n\tthis to /y. Unusued by Unix like systems.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "NA",
            win2k => "/y",
        },
    },
    MAILER        => {
        comment   => "# Mailer command",
        editable  => 1,
        help      => "Complete path and filename for the CABIE mailer ".
                     "application\n\tused for build notification.\n\n\t".
                     "This should typically not be altered.",
        uses      => "BSR:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkfile",
        },
        default   => {
            posix => "support/mailer.pl",
            win2k => "support/mailer.pl",
        },
    },
    SHAREPOINT    => { 
        comment   => "# Network share point to be displayed in mailer.",
        editable  => 1,
        help      => "NFS/SAMBA share that can be clicked on via email ".
                     "sent when\n\ta build has completed.", 
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "fixslashes",
        },
        default   => {
            posix => "\\\\hostname\\share",
            win2k => "\\\\hostname\\share",
        },
    },
    SMTP          => { 
        comment   => "# SMTP servername",
        editable  => 1,
        help      => "SMTP server used for sending build email.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "smtpservername.company.com",
            win2k => "smtpservername.company.com",
        },
    },
    SMTPDELIM     => { 
        comment   => "# Delimeter for SMTP addressing",
        editable  => 1,
        help      => "Delimeter used to separate email addresses passed to ".
                     "the\n\tSMTP server.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => ",",
            win2k => ",",
        },
    },
    NOTIFYFROM    => {
        comment   => "# Return address for build notification",
        editable  => 1,
        help      => "Return address used when sending build notification ".
                     "email.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "addressformat",
        },
        default   => {
            posix => "Build Server <buildmeister\@company.com>",
            win2k => "Build Server <buildmeister\@company.com>",
        },
    },
    ADMIN         => { 
        comment   => "# Address for build admin",
        editable  => 1,
        help      => "This is the buildserver administrators email address.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "addressformat",
        },
        default   => {
            posix => "buildadmin\@company.com",
            win2k => "buildadmin\@company.com",
        },
    },
    ALTERNATE     => { 
        comment   => "# Secondary email addresses for build admin",
        editable  => 1,
        help      => "This is the secondary email address for the ".
                     "buildservers\n\tadministrator, typically an emergency ".
                     "address such as a\n\tpersonal email account.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "addressformat",
        },
        default   => {
            posix => "buildadmin\@homeaddress.com",
            win2k => "buildadmin\@homeaddress.com",
        },
    },
    WEBSERVER     => { 
        comment   => "# Web server where build can be found",
        editable  => 1,
        help      => "Base URL of webserver used to display build ".
                     "information.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "http://buildserver.company.com",
            win2k => "http://buildserver.company.com",
        },
    },
    RLCHG         => { 
        comment   => "# Change number from SCCS (Don't edit)",
        editable  => 0,
        help      => "DO NOT EDIT",
        uses      => "",
        value     => "0",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "0",
            win2k => "0",
        },
    },
    BLDNUM        => { 
        comment   => "# Incremental build number (if defined) (Don't edit)",
        editable  => 0,
        help      => "DO NOT EDIT",
        uses      => "",
        value     => "0",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "0",
            win2k => "0",
        },
    },
    INCREMENT     => { 
        comment   => "# Boolean - always increment build number",
        editable  => 1,
        help      => "Set to 1 to increment build number even if build fails.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkboolean",
        },
        default   => {
            posix => "0",
            win2k => "0",
        },
    },
    LSTCHG        => { 
        comment   => "# For perforce remote depots (Don't edit)",
        editable  => 0,
        help      => "DO NOT EDIT",
        uses      => "",
        value     => "0",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "0",
            win2k => "0",
        },
    },
    PARALLEL      => { 
        comment   => "# Set to 0 if jobs should be run sequentially",
        editable  => 1,
        help      => "By default builds are run sequentially. If you wish ".
                     "builds to\n\trun in parallel set this value to 1.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkboolean",
        },
        default   => {
            posix => "0",
            win2k => "0",
        },
    },
    PRN           => { 
        comment   => "# A product release number (Don't edit)",
        editable  => 0,
        help      => "DO NOT EDIT",
        uses      => "",
        value     => "0",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "0",
            win2k => "0",
        },
    },
    PRF           => { 
        comment   => "# A product release format (Don't edit)",
        editable  => 0,
        help      => "DO NOT EDIT",
        uses      => "",
        value     => "0",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "0",
            win2k => "0",
        },
    },
    PREBUILD      => { 
        comment   => "# name of prebuild trigger",
        editable  => 1,
        help      => "Name of script executed prior to a build.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "prebuild.sh",
            win2k => "prebuild.cmd",
        },
    },
    POSTBUILD     => {
        comment   =>  "# name of postbuild trigger",
        editable  => 1,
        help      => "Name of script executed after sucessfull build.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "postbuild.sh",
            win2k => "postbuild.cmd",
        },
    },
    POSTPOSTBUILD => { 
        comment   => "# name of postpostbuild trigger",
        editable  => 1,
        help      => "Name of script executed after postbuild or failure ".
                     "scripts.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "postpostbuild.sh",
            win2k => "postpostbuild.cmd",
        },
    },
    ONFAIL        => { 
        comment   => "# name of failure trigger",
        editable  => 1,
        help      => "Name of script executed after a failed build.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "onfail.sh",
            win2k => "onfail.cmd",
        },
    },
    RETAIL        => { 
        comment   => "# name of retail build script",
        editable  => 1,
        help      => "Name of build script used to generate non-debug code.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "rtlbuild.sh",
            win2k => "rtlbuild.cmd",
        },
    },
    DEBUG         => { 
        comment   => "# name of debug build script",
        editable  => 1,
        help      => "Name of build script used to generate debug code.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "dbgbuild.sh",
            win2k => "dbgbuild.cmd",
        },
    },
    UPDATEJOBS    => { 
        comment   => "# name of script for updating build scripts",
        editable  => 1,
        help      => "Name of script used to update CABIE components.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "updatejobs.sh",
            win2k => "updatejobs.cmd",
        },
    },
    PROMOTECMD    => { 
        comment   => "# External script called to promote builds.",
        editable  => 1,
        help      => "Name of script used to promote builds. The first arg ".
                     "is the\n\tsource (jobdir) the second arg is the target ".
                     "(promote), then\n\tjobname, jobno.\n\n\tThis is ".
                     "optional!",
        uses      => "BSR:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "promote.sh",
            win2k => "promote.cmd",
        },
    },
    PROMOTION     => { 
        comment   => "# name of parent directory for promoted builds",
        editable  => 1,
        help      => "Name of directory used to copy promoted builds to.",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
            function   => "checkpath",
        },
        default   => {
            posix => "/usr/promotion",
            win2k => "c:/promotion",
        },
    },
    RELEASE       => { 
        comment   => "# name of parent directory for released builds",
        editable  => 1,
        help      => "Name of directory used to copy released builds to.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkpath",
        },
        default   => {
            posix => "/usr/releases",
            win2k => "c:/releases",
        },
    },
    ISADDR        => { 
        comment   => "# Email address for IT to address HW problems",
        editable  => 1,
        help      => "Email address used for reporting issues to IT ".
                     "department.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "addressformat",
        },
        default   => {
            posix => "itdepartment\@company.com",
            win2k => "itdepartment\@company.com",
        },
    },
    OPENFOLD      => { 
        comment   => "# Name and location of open folder icon used by genweb",
        editable  => 1,
        help      => "URL pointing to the icon used for an open folder.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "icons/folder.open.gif",
            win2k => "icons/folder.open.gif",
        },
    },
    CLOSEDFOLD    => { 
        comment   => "# Name and location of open folder icon used by genweb",
        editable  => 1,
        help      => "URL pointing to the icon used for a closed folder.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "icons/folder.gif",
            win2k => "icons/folder.gif",
        },
    },
    GREENICON     => { 
        comment   => "# Name and location of green (success) icon",
        editable  => 1,
        help      => "URL pointing to the icon displayed for a sucessful build.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "icons/greenlight.gif",
            win2k => "icons/greenlight.gif",
        },
    },
    REDICON       => { 
        comment   => "# Name and location of red (build failure) icon",
        editable  => 1,
        help      => "URL pointing to the icon displayed for failed build.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "icons/redlight.gif",
            win2k => "icons/redlight.gif",
        },
    },
    YELLOWICON    => { 
        comment   => "# Name and location of yellow (test failure) icon",
        editable  => 1,
        help      => "URL pointing to the icon displayed for failed build.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "icons/yellowlight.gif",
            win2k => "icons/yellowlight.gif",
        },
    },
    RUNICON       => { 
        comment   => "# Name and location of job running icon",
        editable  => 1,
        help      => "URL pointing to the icon displayed for a running build.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "icons/runner.gif",
            win2k => "icons/runner.gif",
        },
    },
    DISICON       => { 
        comment   => "# Name and location of job disabled icon",
        editable  => 1,
        help      => "URL pointing to the icon displayed for a disabled job.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "icons/smchk_rd.gif",
            win2k => "icons/smchk_rd.gif",
        },
    },
    PROMOICON     => { 
        comment   => "# Icon used for build promotion",
        editable  => 1,
        help      => "URL pointing to the icon displayed for a promoted job.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "icons/disk.gif",
            win2k => "icons/disk.gif",
        },
    },
    POLLFILE      => { 
        comment   => "# File to use for polling job in monitor thread",
        editable  => 1,
        help      => "Name of the file listing jobs to be polled for changes.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "poll.txt",
            win2k => "poll.txt",
        },
    },
    LOGOICON      => { 
        comment   => "# Icon for company logo",
        editable  => 1,
        help      => "URL pointing to the image displayed for company logo.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "icons/logo.gif",
            win2k => "icons/logo.gif",
        },
    },
    CVS           => { 
        comment   => "# Icon for CVS logo",
        editable  => 1,
        help      => "URL pointing to the icon displayed for CVS info.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "icons/cvs.gif",
            win2k => "icons/cvs.gif",
        },
    },
    PERFORCE      => { 
        comment   => "# Icon for Perforce logo",
        editable  => 1,
        help      => "URL pointing to the icon displayed for Perforce info.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "icons/perforce.gif",
            win2k => "icons/perforce.gif",
        },
    },
    CGIBIN        => { 
        comment   => "# Location for cgi-bin used by web interface",
        editable  => 1,
        help      => "URL pointing to the cgi-bin directory used by web ".
                     "interface.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "cgi-bin",
            win2k => "cgi-bin",
        },
    },
    SQLSERVER     => { 
        comment   => "# Name:port of SQL server used for storing build ".
                     "information",
        editable  => 1,
        help      => "Name of server where MySQL is running with a builds ".
                     "table installed.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkdb",
        },
        default   => {
            posix => "mysqlserver.company.com",
            win2k => "mysqlserver.company.com",
        },
    },
    SQLID         => { 
        comment   => "# Name of user with permissions to access MySQL server",
        editable  => 1,
        help      => "Account allowed to access the builds table from the ".
                     "MySQL server.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "mysqluserid",
            win2k => "mysqluserid",
        },
    },
    SQLPASS       => { 
        comment   => "# Password for user with to access MySQL server",
        editable  => 1,
        help      => "Password used to access MySSQL server .",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "",
            win2k => "",
        },
    },
    NOTIFYALL     => { 
        comment   => "# Notify all upon build failures",
        editable  => 1,
        help      => "Boolean value indicating that any build failures need ".
                     "to be\n\tdelivered to a global build email alias.", 
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkboolean",
        },
        default   => {
            posix => "1",
            win2k => "1",
        },
    },
    GLOBMAIL      => { 
        comment   => "# Global email alias for build notification",
        editable  => 1,
        help      => "Global email address used to send build failure and ".
                     "tree\n\tstability messages to.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "addressformat",
        },
        default   => {
            posix => "developers\@company.com",
            win2k => "developers\@company.com",
        },
    },
    P4USER        => { 
        comment   => "# User ID for pulling and updating files from Perforce",
        editable  => 1,
        help      => "Perforce user ID used to access Perforce server.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "perforceid",
            win2k => "perforceid",
        },
    },
    P4PASSWD      => { 
        comment   => "# Password for perforce user",
        editable  => 1,
        help      => "Password used by perforce user to access sources.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "p4passwd",
            win2k => "p4passwd",
        },
    },
    FAILALL       => { 
        comment   => "# Notify for sequestial failures",
        editable  => 1,
        help      => "Boolean value indicating that email should be sent out ".
                     "for\n\tsequential build failures from the same job.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkboolean",
        },
        default   => {
            posix => "0",
            win2k => "0",
        },
    },
    NOTTESTED     => { 
        comment   => "# Name for file to be put in output directory",
        editable  => 1,
        help      => "File created to indicate that a CABIE generated ".
                     "build has\n\tnot been tested.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "BUILD NOT TESTED",
            win2k => "BUILD NOT TESTED",
        },
    },
    TESTED        => { 
        comment   => "# Name for file when build has passed smoketests",
        editable  => 1,
        help      => "File created to indicate that the build has been ".
                     "tested and\n\thas passed tests.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "BUILD PASSED SMOKETESTS",
            win2k => "BUILD PASSED SMOKETESTS",
        },
    },
    PS            => { 
        comment   => "# PS command used to generate process info",
        editable  => 1,
        help      => "Command used to generate process table.",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "ps -ef",
            win2k => "",
        },
    },
    WEBROOT       => { 
        comment   => "# Root for the web server",
        editable  => 1,
        help      => "URL pointing to the root of the webserver.",
        uses      => "WEBSERVER:value",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 1,
            function   => "checkurl",
        },
        default   => {
            posix => "",
            win2k => "",
        },
    },
    STYLESHEET    => {
        comment   => "# Name of the XSL stylesheet to use for rendering XML",
        editable  => 1,
        help      => "Name of XSL stylesheet to use for rendering XML",
        uses      => "",
        value     => "",
        loaded    => "",
        nullval   => "",
        validate  => {
            runroutine => 0,
        },
        default   => {
            posix => "/status.xsl",
            win2k => "/status.xsl",
        },
    },

);

my %sccssystems = (
    perforce =>   {
        title   => 'perforce_example',
        port    => 'server.company.com:1666',
        client  => 'perforce_client',
        top     => {
            win2k => 'c:\src\perforce_example',
            posix => '/src/perforce_example',
        },
        toolsdir  => {
            win2k => 'c:\usr\bin',
            posix => '/usr/bin:/libdir',
        },
        comment => 'Initial perforce example job',
        web     => 'http://p4webserver.company.com:8080/@md=d&cd=//depot'.
                   '/main/&c=4sc@//%d?ac=10',
    },
    cvs         => {
        title   => 'cvs_example',
        port    => ':pserver:build@source:/src',
        client  => 'module1 module2 module3 !module4',
        top     => {
            win2k => 'c:\src\cvs_example',
            posix => '/src/cvs_example',
        },
        toolsdir  => {
            win2k => 'c:\usr\bin',
            posix => '/usr/bin:/libdir',
        },
        comment => 'Initial cvs example job',
        web     => 'http://viewcvs.company.com/viewcvs/viewcvs.cgi/%s#'.
                   'rev%s',
    },
    subversion  => {
        title   => 'subversion_example',
        port    => 'svn://foad',
        client  => 'module1 module2 module3 !module4',
        top     => {
            win2k => 'c:\src\subversion_example',
            posix => '/src/subversion_example',
        },
        toolsdir  => {
            win2k => 'c:\usr\bin',
            posix => '/usr/bin:/libdir',
        },
        comment => 'Initial subversion example job',
        web     => 'http://svnserver:7467/viewvc/%s?revision=%s&root='.
                   'yourcm&view=markuprev',
    },
);

my %pbasepackages = (
    "threads"          => "cpan threads",
    "RPC::XML"         => "cpan RPC::XML",
    "DBI"              => "cpan DBI",
    "DBD::mysql"       => "cpan DBD::mysql",
    "Mail::Sendmail"   => "cpan Mail::Sendmail",
    "Archive::Zip"     => "cpan Archive::Zip",
    "HTTP::Date"       => "cpan HTTP::Date",
);

my %wbasepackages = (
    "threads"              => "cmd /c ppm install threads",
    "DBI"                  => "cmd /c ppm install DBI",
    "DBD::mysql"           => "cmd /c ppm install DBD-mysql",
    "Mail::Sendmail"       => "cmd /c ppm install Mail-Sendmail",
    "Archive::Zip"         => "cmd /c ppm install Archive::Zip",
    "HTTP::Date"           => "cmd /c ppm install HTTP::Date",
    "Win32::Daemon"        => "cmd /c ppm install http://www.roth.net/perl/".
                              "packages/win32-daemon.ppd",
    "Win32::Process"       => "cmd /c ppm install Win32::Process",
    "Win32::Process::Info" => "cmd /c ppm install Win32::Process::Info",
    "Win32::Job"           => "cmd /c ppm install threads",
    "Win32::AdminMisc"     => "cmd /c ppm install http://www.roth.net/perl/".
                              "packages/win32-adminmisc.ppd",
);

if ($POSIX) {
    %basepackages = %pbasepackages;
} else {
    %basepackages = %wbasepackages;
}

$term = new Term::ReadLine 'CABIE Configuation';

_cls();

if ($POSIX) {
    open (F, ">installpackages.sh");
} else {
    open (F, ">installpackages.cmd");
    print F "\@echo off\n";
}

foreach $package (keys %basepackages) {

    if (!(searchmodule("$package"))) {
        print "Missing perl package $package\n";
	print F "$basepackages{$package}\n";
        $bNotReady = 1;
    }

}

close(F);

if ($bNotReady) {
print <<EOF;

	Please install the above missing packages using the following 
        command:

		$installcmd

	Then restart the configuration.

EOF

    $command = $term->readline("Press return to exit: ");

    exit 1;
}

_cls();

print <<EOF;
Found all dependencies for the configuration of CABIE.

	This will configure the CABIE server on $hostname to run in 
        the following directory:

	$basedir

EOF

$command = $term->readline("Press return to continue: ");

if (-f "lib/$configname.pm") {
    _loadconfiguration();
} 

my %schema = (
    activetestimage    => {
        statement =>  'CREATE TABLE activetestimage ('.
                      'server varchar(30) default NULL,'.
                      'deployserver varchar(30) default NULL,'.
                      'imagename varchar(64) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'buildserver varchar(30) default NULL,'.
                      'jobname varchar(30) default NULL,'.
                      'job varchar(30) default NULL,'.
                      'installimage varchar(30) default NULL,'.
                      'imagereference varchar(30) default NULL'.
                      ') TYPE=MyISAM;',
    },
    authtable         => {
        statement =>  'CREATE TABLE authtable ('.
                      'server varchar(30) default NULL,'.
                      'machine varchar(128) default NULL'.
                      ') TYPE=MyISAM;',
    },
    buildservers      => {
        statement =>  'CREATE TABLE buildservers ('.
                      'server varchar(30) default NULL,'.
                      'description blob,'.
                      'port int(6) default NULL,'.
                      'status int(1) default NULL,'.
                      'globalmail varchar(64) default NULL'.
                      ') TYPE=MyISAM;',
    },
    changes           => {
        statement =>  'CREATE TABLE changes ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'job varchar(25) default NULL,'.
                      'changes text'.
                      ') TYPE=MyISAM;',
    },
    comments          => {
        statement =>  'CREATE TABLE comments ('.
                      'server varchar(30) NOT NULL default "",'.
                      'title varchar(30) NOT NULL default "",'.
                      'job varchar(25) NOT NULL default "",'.
                      'comment blob,'.
                      'PRIMARY KEY  (server,title,job),'.
                      'KEY server (server),'.
                      'KEY title (title),'.
                      'KEY job (job),'.
                      'KEY comment (comment(128))'.
                      ') TYPE=MyISAM;',
    },
    configuration => {
        statement =>  'CREATE TABLE configuration ('.
                      'server varchar(30) NOT NULL default "",'.
                      'title varchar(30) NOT NULL default "",'.
                      'port varchar(64) default NULL,'.
                      'client text,'.
                      'top varchar(255) default NULL,'.
                      'type varchar(255) default NULL,'.
                      'toolsdir longtext,'.
                      'isimake varchar(4) default NULL,'.
                      'keeplevel int(4) default NULL,'.
                      'comment longtext,'.
                      'sccs varchar(16) default NULL,'.
                      'browserlink longtext,'.
                      'state int(2) default NULL,'.
                      'spam int(1) default NULL,'.
                      'buildsize bigint(64) default NULL,'.
                      'PRIMARY KEY  (server,title)'.
                      ') TYPE=MyISAM;',
    },
    joblog            => {
        statement =>  'CREATE TABLE joblog ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'message varchar(255) default NULL,'.
                      'step int(2) default NULL'.
                      ') TYPE=MyISAM;',
    }, 
    jobs              => {
        statement =>  'CREATE TABLE jobs ('.
                      'server varchar(30) NOT NULL default "",'.
                      'title varchar(30) NOT NULL default "",'.
                      'job varchar(25) NOT NULL default "",'.
                      'start int(15) default NULL,'.
                      'end int(15) default NULL,'.
                      'status int(2) default NULL,'.
                      'info blob,'.
                      'sccs varchar(16) default NULL,'.
                      'browserlink blob,'.
                      'buildsize bigint(64) default NULL,'.
                      'port varchar(64) default NULL,'.
                      'removed int(1) default NULL,'.
                      'releaseformat char(16) NOT NULL default "",'.
                      'PRIMARY KEY  (server,title,job),'.
                      'KEY server (server),'.
                      'KEY title (title),'.
                      'KEY job (job)'.
                      ') TYPE=MyISAM;',
    },
    locktest          => {
        statement =>  'CREATE TABLE locktest ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'buildjob varchar(30) default NULL'.
                      ') TYPE=MyISAM;',
    },
    monitorservers    => {
        statement =>  'CREATE TABLE monitorservers ('.
                      'server varchar(30) default NULL,'.
                      'port int(6) default NULL,'.
                      'status int(1) default NULL'.
                      ') TYPE=MyISAM;',
    },
    pending           => {
        statement =>  'CREATE TABLE pending ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'changes blob'.
                      ') TYPE=MyISAM;',
    },
    polltest          => {
        statement =>  'CREATE TABLE polltest ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL'.
                      ') TYPE=MyISAM;',
    },
    proctree          => {
        statement =>  'CREATE TABLE proctree ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'job varchar(25) default NULL,'.
                      'cmd blob,'.
                      'pid int(64) default NULL'.
                      ') TYPE=MyISAM;',
    },
    promotion         => {
        statement =>  'CREATE TABLE promotion ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'job varchar(25) default NULL,'.
                      'at varchar(26) default NULL,'.
                      'bywhom varchar(60) default NULL,'.
                      'comment blob'.
                      ') TYPE=MyISAM;',
    },
    removed           => {
        statement =>  'CREATE TABLE removed ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'job varchar(25) default NULL,'.
                      'at varchar(26) default NULL,'.
                      'action varchar(16) default NULL,'.
                      'directory blob'.
                      ') TYPE=MyISAM;',
    },
    restore           => {
        statement =>  'CREATE TABLE restore ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'message varchar(255) default NULL,'.
                      'time int(15) default NULL,'.
                      'state int(2) default NULL'.
                      ') TYPE=MyISAM;',
    },
    results           => {
        statement =>  'CREATE TABLE results ('.
                      'server varchar(30) default NULL,'.
                      'deployserver varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'buildserver varchar(30) default NULL,'.
                      'jobname varchar(30) default NULL,'.
                      'job varchar(30) default NULL,'.
                      'start int(15) default NULL,'.
                      'end int(15) default NULL,'.
                      'teststatus int(1) default NULL'.
                      ') TYPE=MyISAM;',
    },
    run               => {
        statement =>  'CREATE TABLE run ('.
                      'server varchar(30) default NULL,'.
                      'deployserver varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'buildserver varchar(30) default NULL,'.
                      'jobname varchar(30) default NULL,'.
                      'job varchar(30) default NULL,'.
                      'start int(15) default NULL,'.
                      'end int(15) default NULL,'.
                      'state int(1) default NULL,'.
                      'message blob,'.
                      'dependserver varchar(30) default NULL'.
                      ') TYPE=MyISAM;',
    },
    semaphores        => {
        statement =>  'CREATE TABLE semaphores ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'message varchar(255) default NULL,'.
                      'time int(15) default NULL,'.
                      'state int(2) default NULL'.
                      ') TYPE=MyISAM;',
    },
    serverids         => {
        statement =>  'CREATE TABLE serverids ('.
                      'server varchar(30) default NULL,'.
                      'type varchar(30) default NULL,'.
                      'pid varchar(30) default NULL'.
                      ') TYPE=MyISAM;',
    },
    stage             => {
        statement =>  'CREATE TABLE stage ('.
                      'server varchar(30) default NULL,'.
                      'deployserver varchar(30) default NULL,'.
                      'imagename varchar(64) default NULL,'.
                      'bootscript varchar(64) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'buildserver varchar(30) default NULL,'.
                      'jobname varchar(30) default NULL,'.
                      'job varchar(30) default NULL,'.
                      'state int(1) default NULL,'.
                      'installimage varchar(30) default NULL,'.
                      'attempt int(1) default NULL,'.
                      'dependserver varchar(30) default NULL'.
                      ') TYPE=MyISAM;',
    },
    subscription      => {
        statement =>  'CREATE TABLE subscription ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'address varchar(64) default NULL'.
                      ') TYPE=MyISAM;',
    },
    testconfiguration => {
        statement =>  'CREATE TABLE testconfiguration ('.
                      'server varchar(30) default NULL,'.
                      'title varchar(30) default NULL,'.
                      'type int(1) default NULL,'.
                      'imagename varchar(64) default NULL,'.
                      'bootscript varchar(64) default NULL,'.
                      'comment blob,'.
                      'state int(1) default NULL,'.
                      'nextjob varchar(30) default NULL,'.
                      'spam int(1) default NULL,'.
                      'buildjob varchar(30) default NULL,'.
                      'deployto varchar(30) default NULL,'.
                      'platform varchar(30) default NULL,'.
                      'imageserver varchar(30) default NULL,'.
                      'imagereference varchar(30) default NULL,'.
                      'attempts int(11) default NULL,'.
                      'dependserver varchar(30) default NULL,'.
                      'completed int(2) default NULL'.
                      ') TYPE=MyISAM;',
    },
    testserver        => {
        statement =>  'CREATE TABLE testserver ('.
                      'server varchar(30) default NULL,'.
                      'state int(1) default NULL'.
                      ')TYPE=MyISAM;',
    },
    teststate         => {
        statement =>  'CREATE TABLE teststate ('.
                      'title varchar(30) default NULL,'.
                      'buildserver varchar(30) default NULL,'.
                      'jobname varchar(30) default NULL,'.
                      'job varchar(30) default NULL,'.
                      'step varchar(30) default NULL,'.
                      'state int(1) default NULL,'.
                      'start int(15) default NULL,'.
                      'end int(15) default NULL,'.
                      'KEY title (title),'.
                      'KEY job (job)'.
                      ') TYPE=MyISAM;',
    },
    users             => {
        statement =>  "CREATE TABLE users (".
                      "userid mediumint(9) NOT NULL auto_increment,".
                      "username varchar(30) NOT NULL default '',".
                      "userpass varchar(32) NOT NULL default '',".
                      "userrealm varchar(30) NOT NULL default '',".
                      "realmid int(11) NOT NULL default '0',".
                      "PRIMARY KEY  (userid),".
                      "UNIQUE KEY username (username)".
                      ") TYPE=MyISAM;",
    },
);

open(F, ">lib/$configname.pm");
_head();

#
# Read through each configuration parameter
#
foreach $key (sort keys %fields) {

    my $errmsg;
    my $addcommand;
    my $bProceed = 0;

    $errstring = "";

    #
    # Keep prompting for the same value until it has been correctly
    # entered.
    #
    while (!$bProceed) {

        $bProceed = _processentry($key);

    }
 
    print F "\t$key => \'$fields{$key}{value}\', $fields{$key}{comment}\n";

}

_tail();

close(F);

open (MSG, ">updates/$hostname.whatsnew.txt");
my $newmsg = sprintf("$welcome\n", $mon, $mday, $year);
printf MSG $newmsg;
close (MSG);

open(F, ">config/$hostname.disclaimer");
_disclaimer();
close(F);


sub _processentry {

    my $key = shift;

    my $errmsg;

    my $bProceed = 0;

    _cls();

    #
    # Some parameters are not editable, so don't edit those...
    #
    if ($fields{$key}{editable}) {

        #
        # If a value has not been set
        #
        if ($fields{$key}{value} =~ /^$/) {

            #
            # If there's a dependency on another value then process
            # that value first.
            #
            if ($fields{$key}{uses} !~ /^$/) {
                my ($left, $right) = split(/:/, $fields{$key}{uses});
                while (!$bProceed) {
                    $bProceed = _processentry($left);
                }
                _cls();
                my $rcat = $fields{$key}{default}{$type};
                my $lcat = $fields{$left}{value};
                if ($lcat =~ /\\\\/) {
                    $fields{$key}{default}{$type} = "$lcat\\\\$rcat";
                } else {
                    $fields{$key}{default}{$type} = "$lcat/$rcat";
                }
            }

#
# Print keyname and description
#
print <<EOF;        

[Parameter Name]: $key
========================================
$errstring
[Description]:

	$fields{$key}{help}

EOF
            #
            # Set the default value for this key
            #
            if ($fields{$key}{loaded} =~ /^$/) {
                $default = $fields{$key}{default}{$type};
            } else {
                $default = $fields{$key}{loaded};
            }
    
            #
            # Prompt user for a value, but display the default
            #
            $command = $term->readline($prompt." [$default]: ");
    
            #
            # If the user hits return we will use the default
            #
            if ($command =~ /^$/) {
                $addcommand = $default;
            } else {
                $addcommand = $command;
            }

            #
            # Populate the hash with a value to use
            #
            $fields{$key}{value} = "$addcommand";
    
            #
            # Here we see if there's a callback function for the 
            # value submitted by the user
            #
            if ($fields{$key}{validate}{runroutine}) {
                
                #
                # Get function name from the hash
                #
                $function = $fields{$key}{validate}{function};

                #
                # Execute the callback function, and return any error
                # message to the user.  It will continue to be 
                # displayed until it has been fixed.
                #
                $errmsg = &$function($fields{$key}{value}, $key);
    
                #
                # Display the errormessage if it's defined otherwise
                # set bProceed to 1 in order to leave this loop
                #
                if (defined($errmsg)) {
                    $errstring = $errmsg;
                    $fields{$key}{value} = "";
                    return 0;
                } else {
                    $term->addhistory($addcommand);
                    return 1;
                }

            } else {
                #
                # There's no validation or callback, just move to the
                # next value.
                #
                return 1;
            }
        }
    }
    #
    # This key is not editable
    #
    return 1;
}

sub fixslashes {

    my $string = shift;
    my $key    = shift;
    
    if ($string =~ /^\\\\\\\\/) {
        $string =~ s/\\\\\\\\/&&/g;
        $string =~ s/\\\\/&/g;
        $string =~ s/&/\\\\/g;
    } else {
        $string =~ s/\\/\\\\/g;
    }
    
    print "string: $string\n";
    
    $fields{$key}{value} = $string;
    
    return undef;

}


sub searchmodule {
    
    my ($filename) = @_;

    my $found = 0;

    $filename =~ s/::/\//g;

    foreach $line (@INC) {
        if ( -f "$line/$filename" || -f "$line/$filename.pm" ) {
            return 1;
        }
    }

    return 0;

}

#
# Check for boolean value 
#
sub checkboolean {

    my $value = shift;

    if ($value !~ /[0-1]/) { 
        return "\nthis value must be 0 for off or 1 for on\n";
    }

    return undef;

}

#
# Check for the existance of a filename 
#
sub checkfile {

    my $filename = shift;
    my $key      = shift;

    if (! -f $filename) {
        return _concat("no directory found called $filenames");
    } else {
        if (!$POSIX) {
            $filename =~ s/\//\\\\/g;
            $fields{$key}{value} = $filename;
        }
    }

    return undef;

}

#
# Check for the existance of a path 
#
sub checkpath {

    my $pathname = shift;
    my $key      = shift;

    if (! -d "$pathname") {
        eval { mkpath("$pathname") };
        if ($@) {
            return _concat("Directory $pathname does not exist.", 1);
        }
    } 

    if (!$POSIX) {
        $pathname =~ s/\//\\\\/g;
        $fields{$key}{value} = $pathname;
    }

    return undef;

}

#
# Check Database connection and load Schema...
#
sub checkdb {

    my $sqlquery = "show tables";
    my @sqlarray = _run_sql_query("$sqlquery", ";");

    my $recs = @sqlarray;

    my @initialjob;

    if ($sqlarray[0] =~ /^$/) {
        return _concat("unable to connect as $fields{SQLID}{value} to ".
                       "$fields{SQLSERVER}{value}",4);
    }

    #
    #  If there's no defined jobs, then define one for each 
    #  supported sccs.  The user can use the clone function to 
    #  modify each job.
    #
    foreach my $key (keys %sccssystems) {
        $sqlquery = "select * from configuration where server=\"$hostname\" ".
                    "and title=\"$systype"."_"."$sccssystems{$key}{title}\"";
        @sqlarray = _run_sql_query($sqlquery);
        my $recs = @sqlarray;
        if (!$recs) {
            @initialjob = ();
            push @initialjob, "$hostname";   
            push @initialjob, "$systype"."_"."$sccssystems{$key}{title}";   
            push @initialjob, "$sccssystems{$key}{port}";   
            if ($key =~ /^perforce$/) {
                push @initialjob, "$systype"."_"."$sccssystems{$key}{client}";
            } else {
                push @initialjob, "$sccssystems{$key}{client}";   
            }
            push @initialjob, "$sccssystems{$key}{top}{$type}";   
            push @initialjob, "retail";   
            push @initialjob, "$sccssystems{$key}{toolsdir}{$type}";   
            push @initialjob, "no";   
            push @initialjob, "10";   
            push @initialjob, "$sccssystems{$key}{comment}";   
            push @initialjob, "$key";   
            push @initialjob, "$sccssystems{$key}{web}";   
            push @initialjob, "0";
            push @initialjob, "1";
            push @initialjob, "0";
            _run_sql_submit("configuration", @initialjob);
        }
    }

    return undef;

}

#
# Make sure group exists on system 
#
sub checkgroup {

    my $group = shift;
    my @groups;
    my $entry;
    my $bFound = 0;

    if ($POSIX) {
        open (G, "</etc/group") || die "no group file found!";
        @groups = <G>;
        close(G); 

        foreach $entry (@groups) {
            if ($entry =~ /^$group:/) {
                $bFound = 1;
            }
        }

        if (!$bFound) {
            return _concat("Group '$group' does not exist on $hostname.", 3);
        }
    }

    return undef;

}

#
# We won't try to send email but we will check to see if the
# email address has the proper format.
#
sub checkurl {

    my $url = shift;

    #
    # Just make sure there's an \@ sign...
    #
    my @url = split(/\/\//, $url);
    my $fields = @url;

    if ($fields != 2) {
        return _concat("Invalid URL detected!", 2);
    }

    return undef;

}

#
# We won't try to send email but we will check to see if the
# email address has the proper format.
#
sub addressformat {

    my $address = shift;

    #
    # Just make sure there's an \@ sign...
    #
    my @address = split(/\@/, $address);
    my $fields = @address;

    if ($fields != 2) {
        return _concat("Invalid email address!");
    }

    return undef;

}

sub _run_sql_submit {

    my $table  = shift;
    my @values = @_;

    my $picture;
    my $count  = 0;

    my $sqlserver  = $fields{SQLSERVER}{value};
    my $sqlpass    = $fields{SQLPASS}{value};
    my $sqlid      = $fields{SQLID}{value};

    my $dbh;
    my $sth;

    my $ret = 1;

    $dbh = DBI->connect("dbi:mysql:database=builds;host=$sqlserver",
        "$sqlid", "$sqlpass"); 

    if (defined($dbh)) {

        while ($count < @values) {
            $picture .= "?, ";
            $count++;
        }

        $picture =~ s/, $//g;

        $sth = $dbh->prepare("INSERT INTO $table VALUES($picture)");

        $sth->execute(@values);

        $dbh->disconnect;

    } else {
        $ret = 0;
    }

    return $ret;

}

sub _run_sql_remove {

    my $sqlquery   = shift;

    my $sqlserver  = $fields{SQLSERVER}{value};
    my $sqlid      = $fields{SQLID}{value};
    my $sqlpass    = $fields{SQLPASS}{value};

    my $dbh;
    my $sth;

    my $ret        = 1;

    $dbh = DBI->connect("dbi:mysql:database=builds;host=$sqlserver",
        "$sqlid", "$sqlpass");

    if ($sqlquery =~ /\"\"/ && defined($dbh)) {
        $dbh->disconnect;
        undef $dbh;
    }

    if (defined($dbh)) {
        $sth = $dbh->prepare($sqlquery);
        $sth->execute();
        $dbh->disconnect;
    } else {
        $ret = 0;
    }

    return $ret;

}

sub _run_sql_query {

    my $sqlquery   = shift;
    my $delimeter  = shift;
    my $direction  = shift;

    my $sqlserver  = $fields{SQLSERVER}{value};
    my $sqlpass    = $fields{SQLPASS}{value};
    my $sqlid      = $fields{SQLID}{value};

    my $dbh;
    my $sth;
    my $line;
    my $record;

    my @ret;
    my @row;

    my @initialjob;

    my $rv;

    if ($sqlquery =~ /delete from/) {
        return "cannot delete records from query function";
    }

    $dbh = DBI->connect("dbi:mysql:database=builds;host=$sqlserver",
        "$sqlid", "$sqlpass");

    if ($sqlquery =~ /\"\"/ && defined($dbh)) {
       $dbh->disconnect;
       undef $dbh;
    }

    if (defined($dbh)) {

        my $records = 0;

        $sth = $dbh->prepare($sqlquery);

        $rv = $sth->execute();

        #
        # If this statement does not start with 'update'
        #
        if ($sqlquery !~ /^update/ic) {
            while ( @row = $sth->fetchrow_array) {
                $records++;
                $record = "";
                foreach $line (@row) {
                    chomp $line;
                    $record .= "$line"."$delimeter";
                }
    
                $record =~ s/$delimeter$//g;
    
                if ($direction) {
                    unshift @ret, $record;
                } else {
                    push @ret, $record;
                }
    
            }
        }

        #
        # If there's no records we'll create all tables
        #
        if (!$records && $sqlquery =~ /show/ic) {
            my $cdbh;
            my $nv;
            foreach my $key (keys %schema) {
                $cdbh = $dbh->do($schema{$key}{statement});
            }

            #
            # If we reached this point then we can add initial records 
            # to the database for the convienence of the user that can
            # then be cloned.
            #
            foreach my $key (keys %sccssystems) {
                @initialjob = ();
                push @initialjob, "$hostname";   
                push @initialjob, "$systype"."_"."$sccssystems{$key}{title}";   
                push @initialjob, "$sccssystems{$key}{port}";   
                if ($key =~ /^perforce$/) {
                    push @initialjob, "$systype"."_".
                                      "$sccssystems{$key}{client}";
                } else {
                    push @initialjob, "$sccssystems{$key}{client}";   
                }
                push @initialjob, "$sccssystems{$key}{top}{$type}";   
                push @initialjob, "retail";   
                push @initialjob, "$sccssystems{$key}{toolsdir}{$type}";   
                push @initialjob, "no";   
                push @initialjob, "10";   
                push @initialjob, "$sccssystems{$key}{comment}";   
                push @initialjob, "$key";   
                push @initialjob, "$sccssystems{$key}{web}";   
                push @initialjob, "0";
                push @initialjob, "1";
                push @initialjob, "0";
                _run_sql_submit("configuration", @initialjob);
            }
            push @ret, "created";
        }

        $sth->finish();
        $dbh->disconnect;

        return @ret;

    } else {
        return undef;
    }

}

sub _loadconfiguration {

    my @contents;
    open (F, "<lib/$configname.pm");
    @contents = <F>;
    close(F);

    open (F, ">lib/$configname.pm.bak");
    print F @contents;
    close(F);

    foreach $key (sort keys %fields) {
        foreach $entry (@contents) {
            if ($entry =~ /^[\t ]+$key /) {
                my ($left, $right) = split(/=>/, $entry);
                my ($left, $right) = split(/, #/, $right);
                $left =~ s/'//g;
                $left =~ s/^ +//g;
                $fields{$key}{loaded} = $left;
            } 
        }
    }

}

sub _concat {

    my $message = shift;
    my $fix     = shift;

    return "\nError detected:\n\n\t$message\n\nSolution:\n\n\t".
           "$solutions{$fix}\n";

}

sub _cls {

    if ($POSIX) {
        print "\e[2J";
        print "\e[H";
    }

}

sub _head {

print F <<EOF;
#############################################################################
##                Copyright (c) Eric Wallengren, 2002-$year
##                          All Rights Reserved
#############################################################################

#
# Package declaration
#
package $configname;

#
# Use Carp for error handling
#
use Carp;

#
# Configuration section, edit to suite build machine configuration...
# To view configuration information from the server, use the 
# dumpconfig command.
#
my %fields = (
EOF

}

sub _tail {

print F <<EOF;
);

#
# Change to suite job descriptions
#
sub formatjobid {

    #
    # Edit to suite the type of job number to construct from
    # the builder process.  Use ->OPTION where OPTION is 
    # one of the following (from above):
    #     RLCHG   real change no                  (C)
    #     BLDNUM  buildname.build file            (B)
    #     LSTCHG  last change                     (L)
    #     PRN     product release number (static) (P)
    #
    # Use any combination (or all of them) in the printf statement:
    #     sprintf "%s", $self->RLCHG
    #     sprintf "%s.%s", $self->BLDNUM, $self->RLCHG
    #
    # $self->PRF() Must be set using the letters above that are in parens
    # to match the order in IDFORMAT.
    #

    my \$self = shift;

    my \$IDFORMAT;

    if (\$self->PRN) {
        \$self->PRF("P:C:B");
        \$IDFORMAT = sprintf "%s.%s.%s", \$self->PRN, \$self->RLCHG, \$self->BLDNUM;
    } else {
        \$self->PRF("C:B");
        \$IDFORMAT = sprintf "%s.%s", \$self->RLCHG, \$self->BLDNUM;
    }

    return \$IDFORMAT;

}

#
# Do not edit anything below this line...
#
# Object constructor...
#
sub new {

    my \$that = shift;
    my \$class = ref(\$that) || \$that;
    my \$self = {
        %fields,
    };
    bless \$self, \$class;
    return \$self;
}

#
# Autoload definitions in this package...
#
sub AUTOLOAD {

    my \$self = shift;
    my \$type = ref(\$self) || croak "\$self is not an object";
    my \$name = \$AUTOLOAD;
    \$name =~ s/.*://;
    unless (exists \$self->{\$name}) {
        croak "Can't access `\$name` field in an object of class \$type";
    }
    if (\@_) {
        return \$self->{\$name} = shift;
    } else {
        return \$self->{\$name};
    }
}

1;
EOF

}

sub _disclaimer {

    print F <<EOF;
===========================================================
This message is automatically generated by the build server 
running on $hostname. You can access the build server information 
here:

<a $fields{CGIBIN}{value}/genweb>Continuous Automated Build and Integration Environment</a>

EOF

}
